package i18n.aspect;

import i18n.adapter.I18nAcceptLanguageAdapter;
import i18n.annotation.I18n;
import i18n.enums.HttpRequestHeaderAcceptXEnums;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Field;
import java.util.*;

@Aspect
@Component
/*
 * 请确保被国际化实体或被包含国际化实体的类型修饰为 Object 类型 或内置 集合类型 的顶层类型 修饰的，而不是具体自定义类型
 * 搜索并国际化实例与子实例以此类推所有的国际化前缀映射到主字段
 */
public class I18nAspect {

    private final String REQUEST_HEADER_KEY = HttpRequestHeaderAcceptXEnums.ACCEPT_LANGUAGE.getValue();

    @Resource
    private I18nAcceptLanguageAdapter i18nAcceptLanguageAdapter;

    // 方法级别的切点
    @Pointcut("@annotation(i18n)")
    public void annotatedWithTestAnnotation(I18n i18n) {}

    // 类级别的切点
    @Pointcut("@within(i18n)")
    public void withinTestAnnotation(I18n i18n) {}

    // 组合切点，处理两个条件的逻辑 (有 BUG ，null 指针注解实例问题)
    //    @Pointcut("(execution(* *(..)) && @annotation(testAnnotation)) || @within(testAnnotation)")
//    @Pointcut(value = "annotatedWithTestAnnotation(testAnnotation) || withinTestAnnotation(testAnnotation)", argNames = "testAnnotation")
//    public void testAnnotationPointcut(TestAnnotation testAnnotation) {}

    @Around(value = "withinTestAnnotation(i18n)", argNames = "proceedingJoinPoint, i18n")
    public Object aroundAdviceClass(ProceedingJoinPoint proceedingJoinPoint, I18n i18n) throws Throwable {
        return aroundAdvice(proceedingJoinPoint, i18n);
    }

    @Around(value = "annotatedWithTestAnnotation(i18n)", argNames = "proceedingJoinPoint, i18n")
    public Object aroundAdviceMethod(ProceedingJoinPoint proceedingJoinPoint, I18n i18n) throws Throwable {
        return aroundAdvice(proceedingJoinPoint, i18n);
    }

    private Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint, I18n i18n) throws Throwable {
        // Before
//        System.out.println("========== AroundAdvice Before Advice ==========");
//        System.out.println("当前执行类全限定名: "+ proceedingJoinPoint.getTarget().getClass().getName());
//        System.out.println("当前执行类: "+ proceedingJoinPoint.getTarget().getClass().getSimpleName());
//        System.out.println("方法名: "+ proceedingJoinPoint.getSignature().getName());
        //获取方法传入参数列表
        MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature();
//        String[] parameterNames = methodSignature.getParameterNames();
        Object[] args = proceedingJoinPoint.getArgs();
//        System.out.println("方法传入参数列表: " + Arrays.toString(args) + " length: " + args.length);
        //获得 HttpServletRequest 对象 并从请求头中获取 key == REQUEST_HEADER_KEY 的前缀值
        HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
        String language = i18nAcceptLanguageAdapter.convert(request.getHeader(REQUEST_HEADER_KEY)).getValue();
        System.out.println("language == " + request.getHeader(REQUEST_HEADER_KEY) + "  convert == " + language); //浏览器自动传递的 语言字段，我们需要自行映射转换成指定 Bean 实体里的语言字段
        // Method Running
        Object proceed = proceedingJoinPoint.proceed(args);
        //对返回结果进行转换
//        DFSFindField(proceed, proceed, null, null, language, i18n);
        DFSStackFindField(new Node(proceed, proceed, null, null), language, i18n);
        return proceed;
    }

    /**
     * ---- 扫描的引用类型设置 ----
     * 如果实体中有需要国际化的字段，请保证该字段是 Object 类型 或内置 集合类型 的顶层类型 修饰的，而不是具体自定义类型
     * @param clazz 类型参数
     * @return 类类型 | 引用类型 | 数组类型
     */
    private boolean isClass(Class<?> clazz, I18n i18n) {
        if (clazz == null) { return false; }
        if (clazz.isPrimitive()) { return false; }
        return clazz.isAssignableFrom(i18n.resourceType()) || clazz.isAssignableFrom(Object.class) || clazz.isArray() || clazz.isAssignableFrom(List.class) || clazz.isAssignableFrom(Map.class) ||
                clazz.isAssignableFrom(Set.class) ||
                clazz.isAssignableFrom(TreeMap.class) ||
                clazz.isAssignableFrom(TreeSet.class);
    }

    /**
     *  parent 父对象引用
     *  child 子对象引用
     *  field 子对象操作 Field
     *  index 当获取的操作 cutField 为数组时，需要的位置参数
     */
    private static class Node {
        Object parent;
        Object child;
        Field field; // 对于 parent 父对象的 field 的直接操作 child 的引用
        Integer index; //如果 parent 是数组，需要设置索引加以判断
        Map<String, Field> mainFieldMap;
        Map<String, Field> langFieldMap;
        Node(Object parent, Object child, Field field, Integer index) {
            this.parent = parent;
            this.child = child;
            this.field = field;
            this.index = index;
        }
    }

    /**
     * 非递归优化版
     *
     * @param node 操作对象集合
     * @param language 国际化前缀字段
     * @param i18n 国际化注解对象
     */
    private void DFSStackFindField(Node node, String language, I18n i18n) throws IllegalAccessException, InstantiationException {
        if (node == null || i18n == null || node.child == null) { return; }
        Stack<Node> stack = new Stack<>(); //主运行栈
        Stack<Node> buffer = new Stack<>(); //倒置存储弹出 Node
        stack.push(node);
        //检索指定的国际化语言
        boolean isNotNullLanguage = language != null && !language.isEmpty();
        while (!stack.isEmpty()) {
            Node pop = stack.pop();
            if (pop.child == null) { continue; }
            // 判断源类型, 不为初始对象时才执行
            boolean flag = (pop.field != null && pop.child.getClass().isAssignableFrom(i18n.resourceType())) ||
                            (pop.field != null && i18n.prefix()); //关键条件，就算不设置原与目标转换类型VO，也通过国际化前缀进行字段值覆盖
            // 字段分类
            HashMap<String, Field> mainFieldMap = flag ? new HashMap<>() : null; //存储主字段，除了指定映射语言字段（当然也可能包含其它语言的字段）
            HashMap<String, Field> langFieldMap = flag ? new HashMap<>() : null; //存储语言字段
            // 迭代搜索字段
            Field[] fields = pop.child.getClass().getDeclaredFields();
            for (Field field : fields) {
                field.setAccessible(true);
                Class<?> fieldType = field.getType();
                if (isClass(fieldType, i18n)) { //判断类类型且不是原始类型
                    if (fieldType.isArray()) {
                        if (fieldType.getComponentType() != char.class) { // Map 类型 的 key -> value 转换时为 char[] 数组，需要特判
                            Object[] values = (Object[]) field.get(pop.child); //获得数组对象值（引用）
                            if (values != null) {
                                for (int i = 0; i < values.length; ++i) { //需要拿到索引，后期在设置引用值直接定位
                                    Object value = values[i];
                                    if (value != null) stack.push(new Node(pop.child, value, field, i)); //压栈
                                }
                            }
                        }
                    } else {
                        Object value = field.get(pop.child); //获得对象值（引用）
                        if (value != null) stack.push(new Node(pop.child, value, field, null)); //压栈
                    }
                }
                if (flag) {
                    String fieldName = field.getName();
                    if (isNotNullLanguage && fieldName.startsWith(language)) {
                        langFieldMap.put(fieldName, field);
                    } else {
                        mainFieldMap.put(fieldName, field);
                    }
                }
            }
            if (flag) { //当前 POP 加入转换 VO 操作
                pop.mainFieldMap = mainFieldMap;
                pop.langFieldMap = langFieldMap;
                buffer.push(pop); //压栈
            }
        }
        while (!buffer.isEmpty()) {
            Node pop = buffer.pop();
            if (isNotNullLanguage) { //只要需要语言国际化时才执行
                for (Field field : pop.langFieldMap.values()) {
                    field.setAccessible(true);
                    String fieldName = field.getName();
                    //切割出主映射字段名且首字母替换为小写
                    StringBuilder sbMFieldName = new StringBuilder(fieldName.replace(language, ""));
                    char first = (char) (sbMFieldName.substring(0, 1).charAt(0) ^ 32); //首字母转小写 (需规范驼峰命名时)
                    sbMFieldName.setCharAt(0, first);
                    String mFieldName = sbMFieldName.toString();
                    Field mainField = pop.mainFieldMap.get(mFieldName);
                    if (mainField != null) { //实体对应主映射字段不为空
                        mainField.setAccessible(true);
                        mainField.set(pop.child, field.get(pop.child)); //赋值
                    }
                }
            }
            if (! (i18n.resourceType().isAssignableFrom(Object.class) || i18n.targetType().isAssignableFrom(Object.class))) {
                // 转换 VO 对象
                Object targetType = i18n.targetType().newInstance();
                for (Field field : targetType.getClass().getDeclaredFields()) {
                    field.setAccessible(true);
                    String fieldName = field.getName();
                    Field mainField = pop.mainFieldMap.get(fieldName);
                    if (mainField != null) {
                        mainField.setAccessible(true);
                        field.set(targetType, mainField.get(pop.child));
                    }
                }
                // 最重要的一步，地址赋值到父的引用中
                pop.field.setAccessible(true);
                if (pop.index != null) { //父是数组时
                    Object[] arr = (Object[]) pop.field.get(pop.parent);
                    arr[pop.index] = targetType;
                } else { //父是对象时
                    pop.field.set(pop.parent, targetType);
                }
            }
        }
    }

    /**
     * 递归深度搜索
     *
     * @param parent 父对象引用
     * @param child 子对象引用
     * @param cutField 子对象操作 Field
     * @param index 当获取的操作 cutField 为数组时，需要的位置参数
     * @param language 国际化前缀字段
     * @param i18n 国际化注解对象
     */
    private void DFSFindField(Object parent, Object child, Field cutField, Integer index, String language,  I18n i18n) throws IllegalAccessException, InstantiationException {
        if (child == null) { return; }
        Field[] fields = child.getClass().getDeclaredFields();
        // 判断源类型, 不为初始对象时才执行
        boolean flag = cutField != null && child.getClass().isAssignableFrom(i18n.resourceType());
        boolean isNotNullLanguage = language != null && !language.isEmpty();
        // 字段分类
        HashMap<String, Field> mainFieldMap = flag ? new HashMap<>() : null; //存储主字段，除了指定映射语言字段（当然也可能包含其它语言的字段）
        HashMap<String, Field> langFieldMap = flag ? new HashMap<>() : null; //存储语言字段
        for (Field field : fields) {
            field.setAccessible(true);
            Class<?> fieldType = field.getType();
            if (isClass(fieldType, i18n)) { //判断类类型且不是原始类型
                if (fieldType.isArray()) {
                    if (fieldType.getComponentType() != char.class) { // Map 类型 的 key -> value 转换时为 char[] 数组，需要特判
                        Object[] values = (Object[]) field.get(child); //获得数组对象值（引用）
                        if (values != null) {
                            for (int i = 0; i < values.length; ++i) { //需要拿到索引，后期在设置引用值直接定位
                                Object value = values[i];
                                if (value != null) DFSFindField(child, value, field, i, language, i18n);
                            }
                        }
                    }
                } else {
                    Object value = field.get(child); //获得对象值（引用）
                    if (value != null) DFSFindField(child, value, field, null, language, i18n);
                }
            }
            if (flag) {
                String fieldName = field.getName();
                if (isNotNullLanguage && fieldName.startsWith(language)) {
                    langFieldMap.put(fieldName, field);
                } else {
                    mainFieldMap.put(fieldName, field);
                }
            }
        }
        if (flag) {
            executeFieldSetValue(parent, child, cutField, index, language, i18n, isNotNullLanguage, mainFieldMap, langFieldMap);
        }
    }

    private void executeFieldSetValue(Object parent, Object child, Field cutField, Integer index, String language,  I18n i18n, boolean isNotNullLanguage, HashMap<String, Field> mainFieldMap, HashMap<String, Field> langFieldMap) throws IllegalAccessException, InstantiationException {
        if (isNotNullLanguage) { //只要需要语言国际化时才执行
            for (Field field : langFieldMap.values()) {
                field.setAccessible(true);
                String fieldName = field.getName();
                //切割出主映射字段名且首字母替换为小写
                StringBuilder sbMFieldName = new StringBuilder(fieldName.replace(language, ""));
                char first = (char) (sbMFieldName.substring(0, 1).charAt(0) ^ 32); //首字母转小写 (需规范驼峰命名时)
                sbMFieldName.setCharAt(0, first);
                String mFieldName = sbMFieldName.toString();
                Field mainField = mainFieldMap.get(mFieldName);
                if (mainField != null) { //实体对应主映射字段不为空
                    mainField.setAccessible(true);
                    mainField.set(child, field.get(child)); //赋值
                }
            }
        }
        // 转换 VO 对象
        Object targetType = i18n.targetType().newInstance();
        for (Field field : targetType.getClass().getDeclaredFields()) {
            field.setAccessible(true);
            String fieldName = field.getName();
            Field mainField = mainFieldMap.get(fieldName);
            if (mainField != null) {
                mainField.setAccessible(true);
                field.set(targetType, mainField.get(child));
            }
        }
        // 最重要的一步，地址赋值到父的引用中
        cutField.setAccessible(true);
        if (index != null) { //父是数组时
            Object[] arr = (Object[]) cutField.get(parent);
            arr[index] = targetType;
        } else { //父是对象时
            cutField.set(parent, targetType);
        }
    }

}
